//package org.jim.client.ssl;
//
//import java.io.IOException;
//import java.net.InetSocketAddress;
//import java.nio.ByteBuffer;
//import java.nio.channels.SelectionKey;
//import java.nio.channels.Selector;
//import java.nio.channels.ServerSocketChannel;
//import java.nio.channels.SocketChannel;
//import java.nio.channels.spi.SelectorProvider;
//import java.security.SecureRandom;
//import java.util.Iterator;
//
//import javax.net.ssl.SSLContext;
//import javax.net.ssl.SSLEngine;
//import javax.net.ssl.SSLEngineResult;
//import javax.net.ssl.SSLException;
//import javax.net.ssl.SSLSession;
//
///**
// * An SSL/TLS server, that will listen to a specific address and port and serve SSL/TLS connections
// * compatible with the protocol it applies.
// * <p/>
// * After initialization {@link NioSslServer#start()} should be called so the server starts to listen to
// * new connection requests. At this point, start is blocking, so, in order to be able to gracefully stop
// * the server, a {@link Runnable} containing a server object should be created. This runnable should
// * start the server in its run method and also provide a stop method, which will call {@link NioSslServer#stop()}.
// * </p>
// * NioSslServer makes use of Java NIO, and specifically listens to new connection requests with a {@link ServerSocketChannel}, which will
// * create new {@link SocketChannel}s and a {@link Selector} which serves all the connections in one thread.
// *
// * @author <a href="mailto:alex.a.karnezis@gmail.com">Alex Karnezis</a>
// */
//public class NioSslServer extends NioSslPeer {
//
//	/**
//	 * Declares if the server is active to serve and create new connections.
//	 */
//	private boolean active;
//
//    /**
//     * The context will be initialized with a specific SSL/TLS protocol and will then be used
//     * to create {@link SSLEngine} classes for each new connection that arrives to the server.
//     */
//    private SSLContext context;
//
//    /**
//     * A part of Java NIO that will be used to serve all connections to the server in one thread.
//     */
//    private Selector selector;
//
//
//    /**
//     * Server is designed to apply an SSL/TLS protocol and listen to an IP address and port.
//     *
//     * @param protocol - the SSL/TLS protocol that this server will be configured to apply.
//     * @param hostAddress - the IP address this server will listen to.
//     * @param port - the port this server will listen to.
//     * @throws Exception
//     */
//    public NioSslServer(String protocol, String hostAddress, int port, String keyStorePath , String keyStorePassword) throws Exception {
//
//        context = SSLContext.getInstance(protocol);
//        //context.init(createKeyManagers("./src/main/resources/server.jks", "storepass", "keypass"), createTrustManagers("./src/main/resources/trustedCerts.jks", "storepass"), new SecureRandom());
//        context.init(createKeyManagers(keyStorePath, keyStorePassword, keyStorePassword), createTrustManagers(keyStorePath, keyStorePassword), new SecureRandom());
//
//        SSLSession dummySession = context.createSSLEngine().getSession();
//        myAppData = ByteBuffer.allocate(dummySession.getApplicationBufferSize());
//        myNetData = ByteBuffer.allocate(dummySession.getPacketBufferSize());
//        peerAppData = ByteBuffer.allocate(dummySession.getApplicationBufferSize());
//        peerNetData = ByteBuffer.allocate(dummySession.getPacketBufferSize());
//        dummySession.invalidate();
//
//        selector = SelectorProvider.provider().openSelector();
//        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//        serverSocketChannel.configureBlocking(false);
//        serverSocketChannel.socket().bind(new InetSocketAddress(hostAddress, port));
//        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//
//        active = true;
//
//    }
//
//    /**
//     * Should be called in order the server to start listening to new connections.
//     * This method will run in a loop as long as the server is active. In order to stop the server
//     * you should use {@link NioSslServer#stop()} which will set it to inactive state
//     * and also wake up the listener, which may be in blocking select() state.
//     *
//     * @throws Exception
//     */
//    public void start() throws Exception {
//
//    	log.debug("Initialized and waiting for new connections...");
//
//        while (isActive()) {
//            selector.select();
//            Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
//            while (selectedKeys.hasNext()) {
//                SelectionKey key = selectedKeys.next();
//                selectedKeys.remove();
//                if (!key.isValid()) {
//                    continue;
//                }
//                if (key.isAcceptable()) {
//                    accept(key);
//                } else if (key.isReadable()) {
//                    read((SocketChannel) key.channel(), (SSLEngine) key.attachment());
//                }
//            }
//        }
//
//        log.debug("Goodbye!");
//
//    }
//
//    /**
//     * Sets the server to an inactive state, in order to exit the reading loop in {@link NioSslServer#start()}
//     * and also wakes up the selector, which may be in select() blocking state.
//     */
//    public void stop() {
//    	log.debug("Will now close server...");
//    	active = false;
//    	executor.shutdown();
//    	selector.wakeup();
//    }
//
//    /**
//     * Will be called after a new connection request arrives to the server. Creates the {@link SocketChannel} that will
//     * be used as the network layer link, and the {@link SSLEngine} that will encrypt and decrypt all the data
//     * that will be exchanged during the session with this specific client.
//     *
//     * @param key - the key dedicated to the {@link ServerSocketChannel} used by the server to listen to new connection requests.
//     * @throws Exception
//     */
//    private void accept(SelectionKey key) throws Exception {
//
//    	log.debug("New connection request!");
//
//        SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
//        socketChannel.configureBlocking(false);
//
//        SSLEngine engine = context.createSSLEngine();
//        engine.setUseClientMode(false);
//        engine.beginHandshake();
//
//        if (doHandshake(socketChannel, engine)) {
//            socketChannel.register(selector, SelectionKey.OP_READ, engine);
//        } else {
//            socketChannel.close();
//            log.debug("Connection closed due to handshake failure.");
//        }
//    }
//
//    /**
//     * Will be called by the selector when the specific socket channel has data to be read.
//     * As soon as the server reads these data, it will call {@link NioSslServer#write(SocketChannel, SSLEngine, String)}
//     * to send back a trivial response.
//     *
//     * @param socketChannel - the transport link used between the two peers.
//     * @param engine - the engine used for encryption/decryption of the data exchanged between the two peers.
//     * @throws IOException if an I/O error occurs to the socket channel.
//     */
//    @Override
//    protected void read(SocketChannel socketChannel, SSLEngine engine) throws IOException {
//
//        log.debug("About to read from a client...");
//
//        peerNetData.clear();
//        int bytesRead = socketChannel.read(peerNetData);
//        if (bytesRead > 0) {
//            peerNetData.flip();
//            while (peerNetData.hasRemaining()) {
//                peerAppData.clear();
//                SSLEngineResult result = engine.unwrap(peerNetData, peerAppData);
//                switch (result.getStatus()) {
//                case OK:
//                    peerAppData.flip();
//                    log.debug("Incoming message: " + new String(peerAppData.array()));
//                    break;
//                case BUFFER_OVERFLOW:
//                    peerAppData = enlargeApplicationBuffer(engine, peerAppData);
//                    break;
//                case BUFFER_UNDERFLOW:
//                    peerNetData = handleBufferUnderflow(engine, peerNetData);
//                    break;
//                case CLOSED:
//                    log.debug("Client wants to close connection...");
//                    closeConnection(socketChannel, engine);
//                    log.debug("Goodbye client!");
//                    return;
//                default:
//                    throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
//                }
//            }
//
//            write(socketChannel, engine, "Hello! I am your server!".getBytes());
//
//        } else if (bytesRead < 0) {
//            log.error("Received end of stream. Will try to close connection with client...");
//            handleEndOfStream(socketChannel, engine);
//            log.debug("Goodbye client!");
//        }
//    }
//
//    /**
//     * Will send a message back to a client.
//     *
//     * @param key - the key dedicated to the socket channel that will be used to write to the client.
//     * @param message - the message to be sent.
//     * @throws IOException if an I/O error occurs to the socket channel.
//     */
//    @Override
//    protected void write(SocketChannel socketChannel, SSLEngine engine, byte[] data) throws IOException {
//
//        log.debug("About to write to a client...");
//
//        myAppData.clear();
//        myAppData.put(data);
//        myAppData.flip();
//        while (myAppData.hasRemaining()) {
//            // The loop has a meaning for (outgoing) messages larger than 16KB.
//            // Every wrap call will remove 16KB from the original message and send it to the remote peer.
//            myNetData.clear();
//            SSLEngineResult result = engine.wrap(myAppData, myNetData);
//            switch (result.getStatus()) {
//            case OK:
//                myNetData.flip();
//                while (myNetData.hasRemaining()) {
//                    socketChannel.write(myNetData);
//                }
//                log.debug("Message sent to the client: " + data);
//                break;
//            case BUFFER_OVERFLOW:
//                myNetData = enlargePacketBuffer(engine, myNetData);
//                break;
//            case BUFFER_UNDERFLOW:
//                throw new SSLException("Buffer underflow occured after a wrap. I don't think we should ever get here.");
//            case CLOSED:
//                closeConnection(socketChannel, engine);
//                return;
//            default:
//                throw new IllegalStateException("Invalid SSL status: " + result.getStatus());
//            }
//        }
//    }
//
//    /**
//     * Determines if the the server is active or not.
//     *
//     * @return if the server is active or not.
//     */
//    private boolean isActive() {
//        return active;
//    }
//
//}
